﻿/* 
 * FlowPlayer Server Control for ASP.NET 2.0
 * Copyright (C) 2008 Gaiaware AS
 * All rights reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as 
 * published by the Free Software Foundation.
 * 
 * Or if you have purchased the "Professional Edition" from Gaiaware
 * it is distributed to you in person under the Gaia Commercial License
 * which makes it possible for you to create closed source applications
 * and still be able to distribute those applications without creating 
 * restrictions for your source code.
 * 
 * Unless you're developing GPL software you should and probably legally
 * have to purchase a license of the Gaia Commercial License since otherwise
 * you might be forced to GPL license your derived works.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should find a copy of both the GPL version 3 and the 
 * Gaia Commercial License on disc where you have extracted these files.
 * If not visit http://www.gnu.org for the GPL version or 
 * http://gaiaware.net for the Gaia Commercial License.
 * 
 * Breaking the terms of whatever license you're entitled to use will cause
 * prosecution by Gaiaware AS. If possible we will demand to settle any 
 * law suits at a court in Norway.
 * 
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Web.UI;
using NetServ.Net.Json;
using ASP = System.Web.UI.WebControls;
using Gaia.WebWidgets;
using Gaia.WebWidgets.HtmlFormatting;

[assembly: WebResource("Gaia.Ajax.UI.WebControls.VideoPlayers.Scripts.flashembed.min.js", "text/javascript")]
[assembly: WebResource("Gaia.Ajax.UI.WebControls.VideoPlayers.Scripts.FlowPlayer.js", "text/javascript")]
[assembly: WebResource("Gaia.Ajax.UI.WebControls.VideoPlayers.Resources.FlowPlayerDark.swf", "application/x-shockwave-flash")]

namespace Gaia.Ajax.UI.WebControls.VideoPlayers
{
    /// <summary>
    /// FlowPlayer wraps the FlowPlayer into an ajaxified ASP.NET Server control that can easily be controlled from 
    /// the server. The control can be updated in callbacks and it disptaches useful events
    /// </summary>
    [DefaultProperty("VideoFile")]
    [DefaultEvent("PlayerStateChanged")]
    [ToolboxBitmap(typeof(FlowPlayer), "Gaia.Ajax.UI.WebControls.VideoPlayers.Resources.FlowPlayer.bmp")]
    public class FlowPlayer : GaiaControl, IAjaxControl
    {
        #region [-- FlowPlayer private members and helper properties for serializing method calls --]

        private readonly List<CuePoint> CuePoints = new List<CuePoint>();

        // Backing fields to provide default values on properties
        private bool controlsOverVideo = true;
        private bool showMuteVolumeButton = true;
        private bool showFullScreenButton = true;
        private bool autoPlay = true;
        private int initialVolumePercentage = 50;
        private bool showMenu = true;
        private bool showVolumeSlider = true;
        private bool showScrubber = true;
        private bool autoBuffering = true;
        private ASP.Unit width;
        private ASP.Unit height;
        private bool showStopButton;
        private InitialScale initScale = InitialScale.Scale;

        [AjaxSerializable("setPlay")]
        private bool play { get; set; }

        [AjaxSerializable("setStop")]
        private bool stop { get; set; }

        [AjaxSerializable("setPause")]
        private bool pause { get; set; }

        [AjaxSerializable("setSeek")]
        private int seek { get; set; } 
        #endregion

        #region [-- FlowPlayer Enums --]
        /// <summary>
        /// How will the FLV file be scaled
        /// </summary>
        public enum InitialScale
        {
            /// <summary>
            /// Fit to window by preserving the aspect ratios encoded in the FLV metadata. 
            /// </summary>
            Fit,

            /// <summary>
            /// Half size (preserves aspect ratios)
            /// </summary>
            Half,

            /// <summary>
            /// Use the dimensions encoded in FLV. If the video is too big for the available space the video is scaled as if using the 'fit' option.
            /// </summary>
            Orig,

            /// <summary>
            /// Scale the video to fill all available space for the video. Ignores the dimensions in metadata. 
            /// </summary>
            Scale
        }

        /// <summary>
        /// What state is the FlowPlayer currently in
        /// </summary>
        public enum PlayerState
        {
            /// <summary>
            /// Is Playing
            /// </summary>
            Playing,

            /// <summary>
            /// Is Paused
            /// </summary>
            Paused,

            /// <summary>
            /// Is Resumed
            /// </summary>
            Resumed,

            /// <summary>
            /// Is Stopped
            /// </summary>
            Stopped,

            /// <summary>
            /// The player is in an undefined state
            /// </summary>
            Undefined
        } 
        #endregion

        #region [-- FlowPlayer Events --]

        /// <summary>
        /// Event fires when the FlowPlayer status changes. 
        /// </summary>
        public event EventHandler<StatusChangedEventArgs> PlayerStateChanged;

        /// <summary>
        /// Event fires when a custom user CuePoint has been reached. CuePoint Data is passed on the EventArgs
        /// </summary>
        public event EventHandler<CuePointReachedEventArgs> CuePointReached;

        /// <summary>
        /// Event fires when the video has finished playing
        /// </summary>
        public event EventHandler<EventArgs> VideoClipFinished;

        #endregion

        #region [-- FlowPlayer Custom Event Args --]

        /// <summary>
        /// Event Arguments forwarded as part of the StatusChanged event. Contains current FlowPlayer Status
        /// </summary>
        public class StatusChangedEventArgs : EventArgs
        {
            public PlayerState CurrentState { get; set; }

            internal StatusChangedEventArgs(PlayerState currentState)
            {
                CurrentState = currentState;
            }
        }

        /// <summary>
        /// Event Arguments forwarded when a CuePoint has been reached in the player. Contains 
        /// Information about the cue that was passed down to the client. 
        /// </summary>
        public class CuePointReachedEventArgs : EventArgs
        {
            public CuePoint CuePoint { get; set; }

            internal CuePointReachedEventArgs(CuePoint cuePoint)
            {
                CuePoint = cuePoint;
            }
        }

        #endregion

        #region [-- FlowPlayer Ajax Methods --]

        [Method]
        internal void CuePointReachedMethod(string name, string time)
        {
            if (CuePointReached == null) return;

            var cuePoint = new CuePoint(name, new TimeSpan(0, 0, int.Parse(time)));
            CuePointReached(this, new CuePointReachedEventArgs(cuePoint));
        }

        [Method]
        internal void PlayStatusChangedMethod(string state)
        {
            var playerState = (PlayerState)Enum.Parse(typeof(PlayerState), state);

            if (PlayerStateChanged != null)
                PlayerStateChanged(this, new StatusChangedEventArgs(playerState));
        }

        [Method]
        internal void VideoClipFinishedMethod()
        {
            if (VideoClipFinished != null)
                VideoClipFinished(this, new EventArgs());
        }


        #endregion

        #region [-- FlowPlayer Public Functions --]
        /// <summary>
        /// Seek in the Video to a given TimeSpan
        /// </summary>
        /// <param name="timeSpan"></param>
        public void Seek(TimeSpan timeSpan)
        {
            seek = (int)timeSpan.TotalSeconds;
        }

        /// <summary>
        /// Play Video
        /// </summary>
        public void Play()
        {
            play = true;
        }

        /// <summary>
        /// Stop Video
        /// </summary>
        public void Stop()
        {
            stop = true;
        }

        /// <summary>
        /// Pause Video
        /// </summary>
        public void Pause()
        {
            pause = true;
        }

        /// <summary>
        /// Add a CuePoint to the video
        /// </summary>
        /// <param name="newCuePoint"></param>
        public void AddCueuePoint(CuePoint newCuePoint)
        {
            CuePoints.Add(newCuePoint);
        }

        /// <summary>
        /// Updates the player with new Configuration. Only use this method in gaia callbacks. 
        /// </summary>
        public void UpdatePlayer()
        {
            // The UpdatePlayer call must be made after all property changes. That's why we use this approach here

            string cuePoints = GetCuePointsAsJSON();
            Manager.Instance.AddScriptForClientSideEval(string.Format("$G('{0}').updatePlayer({1}, {2});", ClientID, (!string.IsNullOrEmpty(cuePoints) ? cuePoints : "null" ), GetConfigurationAsJSON()));
        } 
        #endregion

        #region [-- FlowPlayer Public Properties --]

        // flash player settings
        [Description("If the user doesn't have Flash you can specify custom content here")]
        [Category("FlowPlayer Configuration")]
        public string AlternativeContent { get; set; }
        
        /// <summary>
        /// Width of the Flash Player. This value is also propagated to the construction of the Flash Object. 
        /// </summary>
        [DefaultValue("400px")]
        [Category("FlowPlayer Configuration")]
        [Description("Width of the FlowPlayer")]
        public ASP.Unit Width
        {
            get
            {
                if (width.IsEmpty)
                    return new ASP.Unit("400px");

                return width;
            }
            set { width = value; }
        }

        
        /// <summary>
        /// Height of the Flash Player. This value is also propagated to the construction of the Flash Object. 
        /// </summary>
        [DefaultValue("290px")]
        [Category("FlowPlayer Configuration")]
        [Description("Height of the FlowPlayer")]
        public ASP.Unit Height
        {
            get
            {
                if (height.IsEmpty)
                    return new ASP.Unit("290px");
                return height;
            }
            set { height = value; }
        }

        /// <summary>
        /// Specify the ControlBarBackgroundColor to be used. 
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Background color of ControlBar")]
        public Color ControlBarBackgroundColor { get; set; }
        
        /// <summary>
        /// Specify what FlowPlayer to use. If not specified, use inline flowplayer
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Specify a different FlowPlayer to use. The built-in is FlowPlayerDark.swf.")]
        public string FlowPlayerSrc { get; set; }

        /// <summary>
        /// Whether to start loading the video stream into buffer memory immediately. Only meaningful if autoPlay is set 
        /// to false default: true.
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Will automatically start to download the video and buffer up the content if AutoPlay is set to false")]
        [DefaultValue(true)]
        public bool AutoBuffering
        {
            get { return autoBuffering; }
            set { autoBuffering = value; }
        }

        /// <summary>
        /// Specify the File you want FlowPlayer to Play
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Specify the video you want to play in FlowPlayer")]
        public string VideoFile { get; set; }

        /// <summary>
        /// Useful if 'autoPlay' is set to false and you want to show a welcome image before the video is played. 
        /// Must be JPG format. value is relative to the HTML page.
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("If AutoPlay is set to false you can specify a splash image instead here")]
        public string SplashImageFile { get; set; }

        /// <summary>
        /// Allows the user to stop the video explicitly
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Allows the users to stop the video explicitly.")]
        [DefaultValue(false)]
        public bool ShowStopButton
        {
            get { return showStopButton; }
            set { showStopButton = value; }
        }


        /// <summary>
        /// Overlay the ControlPanel on top of the Video.
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("If true will overlay the controls on top of the video playing. Nice effect.")]
        [DefaultValue(true)]
        public bool ControlsOverVideo
        {
            get { return controlsOverVideo; }
            set { controlsOverVideo = value; }
        }

        /// <summary>
        /// Show the Scrubber
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [DefaultValue(true)]
        public bool ShowScrubber
        {
            get { return showScrubber; }
            set { showScrubber = value; }
        }

        /// <summary>
        /// Show the VolumeSlider
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Show the Volume slider on the FlowPlayer that allows users to adjust volume")]
        [DefaultValue(true)]
        public bool ShowVolumeSlider
        {
            get { return showVolumeSlider; }
            set { showVolumeSlider = value; }
        }

        /// <summary>
        /// Show the MUTE volume button
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Show the Mute Volume button on the FlowPlayer")]
        [DefaultValue(true)]
        public bool ShowMuteVolumeButton
        {
            get { return showMuteVolumeButton; }
            set { showMuteVolumeButton = value; }
        }

        /// <summary>
        /// Show full screen button
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Enable or disable the Full Screen button")]
        [DefaultValue(true)]
        public bool ShowFullScreenButton
        {
            get { return showFullScreenButton; }
            set { showFullScreenButton = value; }
        }

        /// <summary>
        /// Show Flash Menu
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Enable or disable the Flash Context menu")]
        [DefaultValue(false)]
        public bool ShowFlashMenu { get; set; }

        /// <summary>
        /// Show the menu on the FlowPlayer
        /// </summary>
        [Description("Enable or disable the FlowPlayer menu")]
        [Category("FlowPlayer Configuration")]
        [DefaultValue(true)]
        public bool ShowMenu
        {
            get { return showMenu; }
            set { showMenu = value; }
        }

        /// <summary>
        /// Iitial video scaling. This can be then changed by the user through the menu. Available options are:
        /// * 'fit': Fit to window by preserving the aspect ratios encoded in the FLV metadata. This is the default setting.
        /// * 'half': Half size (preserves aspect ratios)
        /// * 'orig': Use the dimensions encoded in FLV. If the video is too big for the available space the video is scaled as if using the 'fit' option.
        /// * 'scale': Scale the video to fill all available space for the video. Ignores the dimensions in metadata.
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Initial video scaling")]
        public InitialScale InitScale
        {
            get { return initScale; }
            set { initScale = value; }
        }

        /// <summary>
        /// Initial volume level in percents. Optional, defaults to 50. 
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("Valid values are from 0-100. Specify the initial volume of the player")]
        [DefaultValue(50)]
        public int InitialVolumePercentage
        {
            get { return initialVolumePercentage; }
            set
            {
                if (value > 100 || value < 0)
                    throw new Exception("Valid values for Initial Volumn are between 0 and 100");

                initialVolumePercentage = value;
            }
        }

        /// <summary>
        /// Whether the player starts playback immediately when it's loaded into the browser. default: true
        /// </summary>
        [Category("FlowPlayer Configuration")]
        [Description("AutoPlay the Video upon load")]
        [DefaultValue(true)]
        public bool AutoPlay
        {
            get { return autoPlay; }
            set { autoPlay = value; }
        }

        #endregion

        #region [-- FlowPlayer overriden base class and rendering -- ]

        protected override void RenderControlHtml(XhtmlTagFactory create)
        {
            using (create.Div(ClientID).SetStyle("width: {0}; height: {1}", Width, Height))
            {
                using (create.Div(ClientID + "_object"))
                {
                    if (!string.IsNullOrEmpty(AlternativeContent))
                    {
                        using (create.P().WriteContent(AlternativeContent)) { }
                    }
                }
            }
        }

        protected override void IncludeScriptFiles()
        {
            base.IncludeScriptFiles();
            Manager.Instance.AddInclusionOfFileFromResource("Gaia.Ajax.UI.WebControls.VideoPlayers.Scripts.flashembed.min.js", typeof(FlowPlayer), "Gaia.Extensions.FlowPlayer.browserFinishedLoading");
            Manager.Instance.AddInclusionOfFileFromResource("Gaia.Ajax.UI.WebControls.VideoPlayers.Scripts.FlowPlayer.js", typeof(FlowPlayer), "Gaia.Extensions.FlowPlayer.browserFinishedLoading");
        }

        string IAjaxControl.GetScript()
        {
            string player = !string.IsNullOrEmpty(FlowPlayerSrc) ? FlowPlayerSrc : EmbeddedPlayer;
            string cuePointsAsJSON = GetCuePointsAsJSON();

            return new RegisterControl("Gaia.Extensions.FlowPlayer", ClientID)
                .AddProperty("flowPlayerSrc", player)
                .AddPropertyIfTrue(!string.IsNullOrEmpty(cuePointsAsJSON), "cuePoints", cuePointsAsJSON, false)
                .AddPropertyIfTrue(PlayerStateChanged != null, "listenToStateChanges", true)
                .AddPropertyIfTrue(VideoClipFinished != null, "listenToClipDone", true)
                .AddProperty("cfg", GetConfigurationAsJSON(), false)
                .AddProperty("showFlashMenu", ShowFlashMenu)
                .AddProperty("width", Width.ToString())
                .AddProperty("height", Height.ToString())
                .ToString();
        } 
        #endregion

        #region [-- FlowPlayer Helper Functions ]

        /// <summary>
        /// If the user didn't specify a FlowPlayer, we use the classic Player embedded in the assembly.
        /// </summary>
        private string EmbeddedPlayer
        {
            get
            {
                return Page.ClientScript.GetWebResourceUrl(GetType(),
                    "Gaia.Ajax.UI.WebControls.VideoPlayers.Resources.FlowPlayerDark.swf");
            }
        }

        /// <summary>
        /// Helper function to take the CuePoints and JSONify them
        /// </summary>
        /// <returns></returns>
        private string GetCuePointsAsJSON()
        {
            if (CuePoints.Count == 0)
                return string.Empty;

            var items = new JsonArray();
            foreach (var cuePoint in CuePoints)
                items.Add(cuePoint.ToJSON());

            return items.ToJSONString();
        }

        /// <summary>
        /// JSONifies the FlowPlayer configuration from the Server settings.
        /// </summary>
        /// <returns></returns>
        private string GetConfigurationAsJSON()
        {
            var config = new JsonObject
                             {
                                 {"autoPlay", AutoPlay},
                                 {"autoBuffering", AutoBuffering},
                                 {"showStopButton", ShowStopButton},
                                 {"showScrubber", ShowScrubber},
                                 {"showVolumeSlider", ShowVolumeSlider},
                                 {"showMuteVolumeButton", ShowMuteVolumeButton},
                                 {"showFullScreenButton", ShowFullScreenButton},
                                 {"showMenu", ShowFlashMenu},
                                 {"initialVolumePercentage", InitialVolumePercentage},
                                 {"usePlayOverlay", true},
                                 {"initialScale", InitScale.ToString().ToLower()},
                                 {"videoFile", VideoFile}
                             };

            if (!string.IsNullOrEmpty(SplashImageFile))
                config.Add("splashImageFile", SplashImageFile);

            if (ControlsOverVideo)
            {
                config.Add("controlsOverVideo", "locked");
                config.Add("controlBarBackgroundColor", -1);
            }
            else
            {
                config.Add("controlBarBackgroundColor", ControlBarBackgroundColor.ToArgb().ToString());
            }

            return new JsonObject {{"config", config}}.ToJSONString();

        }

        #endregion
    }

    /// <summary>
    /// Extension methods for JSON handling
    /// </summary>
    public static class JSONExtensions
    {
        /// <summary>
        /// Writes a JSONType to a string and returns it
        /// </summary>
        /// <param name="type">JSONType</param>
        /// <returns>the object as a string</returns>
        public static string ToJSONString(this IJsonType type)
        {
            string json;
            using (var jsonWriter = new JsonWriter())
            {
                type.Write(jsonWriter);
                json = jsonWriter.ToString();
            }

            return json;
        }
    }

}
